﻿using System;
using System.Collections.Generic;
using System.Linq;
using Citi.GocAttributes.Domain;
using Rule = Citi.GocAttributes.Domain.Rule;
using System.Threading;

namespace Citi.GocAttributes.Domain.Utils
{
    /// <summary>
    /// Long Running Class.
    /// </summary>
    public abstract class RuleApplication
    {
        private static object syncRoot = new object();
        public static IDictionary<string, string> ProcessStatus { get; set; }
        public string Id { get; set; }

        public abstract void Status(string message);

        public RuleApplication()
        {
            if (ProcessStatus == null)
                ProcessStatus = new Dictionary<string, string>();
        }

        public string ProcessLongRunningAction(string id, DateTime referenceDate)
        {
            Id = id;

            Status("Starting");
            Apply(referenceDate);
            Status("Rules successfully applied!");

            return id;
        }

        public void Add(string id)
        {
            lock (syncRoot)
                ProcessStatus.Add(id, "Starting");
        }

        public void Remove(string id)
        {
            lock (syncRoot)
                ProcessStatus.Remove(id);
        }

        public string GetStatus(string id)
        {
            lock (syncRoot)
                return ProcessStatus[id];
        }

        public void Apply(DateTime referenceDate)
        {
            Status("Getting GOCS");
            var gocs = new Goc().GetGocsApplyRuleByReferenceDate(referenceDate);
            Status(string.Format("{0} GOCS selected", gocs.Count()));

            Status("Getting Valid Rules");
            var rules = new Rule().GetActiveValidRulesButException();
            Status(string.Format("{0} Valid Rules selected", rules.Count()));

            Apply(ref gocs, rules);
            Status("Rules successfully applied!");

            Thread.Sleep(1000);
        }

        public void UpdateViewOtherSystems()
        {
            Status("Updating Goc Functions View (for other systems purpose)");
            new GocRule().UpdateGocRuleHierarchy();
            Status("View updated");
        }

        private void Apply(ref IEnumerable<Goc> gocs, IEnumerable<Rule> rules)
        {
            if (!gocs.Any() || gocs.Count() <= 0) return;

            Status("Removing previous rules");

            new Domain.GocRule().DeletePreviousRules();
            //var currentRulesApplied = new Rule().SelectAllActive<GocRule>().Where(x => x.ReferenceDate.Month == DateTime.Today.Month && x.ReferenceDate.Year == DateTime.Today.Year).ToList();
            //currentRulesApplied.ForEach(x => x.Delete(x));

            Status("Applying general rules");
            ApplyRules(gocs, rules.Where(x => !x.RuleConditions.Any(y => y.Type == (int)DatabaseEnums.RuleType.GOC)));

            Status("Applying GOC Specific rules");
            ApplyRules(gocs, rules.Where(x => x.RuleConditions.Any(y => y.Type == (int)DatabaseEnums.RuleType.GOC)));

            Status("Rules application is finished");
        }

        private void ApplyRules(IEnumerable<Goc> gocs, IEnumerable<Rule> rules)
        {
            foreach (var rule in rules.OrderBy(x => x.Order).ThenBy(x => x.RuleConditions.Count()).ThenByDescending(x => x.ReferenceDate))
            {
                var gocsSelecionados = gocs.AsEnumerable().ToList();

                foreach (var ruleCondition in rule.RuleConditions.OrderByDescending(x => x.Type))
                {
                    switch ((DatabaseEnums.RuleType)ruleCondition.Type)
                    {
                        case DatabaseEnums.RuleType.Country:
                            gocsSelecionados = gocsSelecionados.Where(x =>
                                x.Country.Abbreviation.ToUpper() == ruleCondition.Value.ToUpper()).ToList();
                            break;
                        case DatabaseEnums.RuleType.OU:
                            int parse;
                            if (!int.TryParse(ruleCondition.Value, out parse)) continue;
                            gocsSelecionados = gocsSelecionados.Where(x => x.FrsOu.Code == parse).ToList();
                            break;
                        case DatabaseEnums.RuleType.LVID:
                            gocsSelecionados = gocsSelecionados.Where(x => x.LegalVehicle.Code == ruleCondition.Value).ToList();
                            break;
                        case DatabaseEnums.RuleType.ManagedSegment:
                            gocsSelecionados = GetGocsByManagedSegment(gocsSelecionados, ruleCondition);
                            break;
                        case DatabaseEnums.RuleType.Function:
                            gocsSelecionados = GetGocsByFunction(gocsSelecionados, ruleCondition);
                            break;
                        case DatabaseEnums.RuleType.GOC:
                            gocsSelecionados = GetGocsByGocNumber(gocsSelecionados, ruleCondition);
                            break;
                        case DatabaseEnums.RuleType.SID:
                            gocsSelecionados = gocsSelecionados.Where(x => x.Sid.Description == ruleCondition.Value).ToList();
                            break;
                        case DatabaseEnums.RuleType.ManagedSegmentDescription:
                            gocsSelecionados = GetGocsByManagedSegmentDescription(gocsSelecionados, ruleCondition);
                            break;
                    }

                    if (!gocsSelecionados.Any())
                        break;
                }

                if (!gocsSelecionados.Any())
                    continue;

                Status(string.Format("{0} GOCS found in rule '{1}'", gocsSelecionados.Count(), rule.Name));

                foreach (var goc in gocsSelecionados)
                {
                    var currentRules = goc.GocRules.Where(x => x.ReferenceDate.Year == DateTime.Today.Year && x.ReferenceDate.Month == DateTime.Today.Month);

                    goc.GocRules.Add(new GocRule
                    {
                        Goc = goc,
                        Order = currentRules.Count(),
                        ReferenceDate = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1),
                        Rule = rule
                    });

                    var gocAttributes = goc.GocAttribute;
                    goc.GocAttribute = string.Empty;
                    goc.GocAttribute = gocAttributes;
                }

                if (gocsSelecionados.Any() && gocsSelecionados.Count > 0)
                    gocsSelecionados.First().Save(gocsSelecionados, 1500);
            }
        }

        private List<Goc> GetGocsByGocNumber(List<Goc> gocsSelecionados, RuleCondition ruleCondition)
        {
            var gocsToSelect = ruleCondition.Value.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim());

            return gocsSelecionados.Where(x => gocsToSelect.Any(y => y == x.GocAttribute)).ToList();
        }

        private List<Goc> GetGocsByManagedSegmentDescription(List<Goc> gocsSelecionados, RuleCondition ruleCondition)
        {
            var gocsManagedSegment = gocsSelecionados.AsEnumerable();
            gocsSelecionados = new List<Goc>();

            foreach (var goc in gocsManagedSegment)
            {
                var managedSegments = new List<ManagedSegment>();
                managedSegments.Add(goc.ManagedSegment);

                var managedSegment = goc.ManagedSegment;

                while (managedSegment.Parent != null)
                {
                    managedSegments.Add(managedSegment.Parent);
                    managedSegment = managedSegment.Parent;
                }

                if (managedSegments.Any(x => x.Description.Contains(ruleCondition.Value)))
                    gocsSelecionados.Add(goc);
            };

            return gocsSelecionados;
        }

        private List<Goc> GetGocsByManagedSegment(List<Goc> gocsSelecionados, RuleCondition ruleCondition)
        {
            var gocsManagedSegment = gocsSelecionados.AsEnumerable();
            gocsSelecionados = new List<Goc>();

            foreach (var goc in gocsManagedSegment)
            {
                var managedSegment = goc.ManagedSegment;
                var itemNotFound = false;

                while (managedSegment.TreeLevel > ruleCondition.ManagedSegment.TreeLevel)
                {
                    if (managedSegment.Parent == null)
                    {
                        itemNotFound = true;
                        break;
                    }
                    managedSegment = managedSegment.Parent;
                }

                if (!itemNotFound && managedSegment.TreeLevel == ruleCondition.ManagedSegment.TreeLevel && managedSegment.Code == ruleCondition.ManagedSegment.Code)
                    gocsSelecionados.Add(goc);
            };
            return gocsSelecionados;
        }

        private List<Goc> GetGocsByFunction(List<Goc> gocsSelecionados, RuleCondition ruleCondition)
        {
            var gocsFunction = gocsSelecionados.AsEnumerable();
            gocsSelecionados = new List<Goc>();

            foreach (var goc in gocsFunction)
            {
                var function = goc.Function;
                var itemNotFound = false;

                while (function.TreeLevel > ruleCondition.Function.TreeLevel)
                {
                    if (function.Parent == null)
                    {
                        itemNotFound = true;
                        break;
                    }
                    function = function.Parent;
                }

                if (!itemNotFound && function.TreeLevel == ruleCondition.Function.TreeLevel && function.Code == ruleCondition.Function.Code)
                    gocsSelecionados.Add(goc);
            };
            return gocsSelecionados;
        }
    }
}